package lion.framework.core.web.path;

import java.io.IOException;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.lang.reflect.Modifier;
import java.lang.reflect.Parameter;
import java.util.Date;

import javax.servlet.AsyncContext;
import javax.servlet.ServletRequest;
import javax.servlet.ServletResponse;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import javax.servlet.http.HttpSession;

import org.apache.commons.fileupload.FileUploadException;
import org.apache.commons.lang.StringUtils;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;

import lion.dev.lang.BeanUtil;
import lion.dev.lang.Lang;
import lion.dev.lang.MapJ;
import lion.dev.lang.Proxy;
import lion.framework.core.bean.BeanFactory;
import lion.framework.core.conf.Config;
import lion.framework.core.conf.ConfigManager;
import lion.framework.core.web.anno.Param;
import lion.framework.core.web.exception.ExceptionView;
import lion.framework.core.web.exception.WebException;
import lion.framework.core.web.request.Request;
import lion.framework.core.web.views.IView;
import lion.framework.core.web.views.NoopView;

/**
 * web 方法执行类,根据路径找到对应的方法，并执行方法，渲染结果返回
 *
 * <pre>
 * time   2013 2013-4-24 上午11:33:01
 * mail   hl_0074@sina.com
 * </pre>
 *
 * @author lion
 */
public class PathNodeExecutor {

	private PathNode node;
	private static Log log = LogFactory.getLog(WebException.class);

	public PathNodeExecutor(PathNode node) {

		this.node = node;
	}

	public void execute(HttpServletRequest request, HttpServletResponse response) throws Exception {

		if (!"ALL".equalsIgnoreCase(node.getMethodType())
				&& !request.getMethod().equalsIgnoreCase(node.getMethodType())) { throw new WebException(405,
						"Method Not Allowed For " + request.getMethod()); }

		String view = executeRequest(request, response);

		executeView(request, response, view);
	}

	/**
	 * 执行excute的方法可以自动注入的参数有：{@link Request}、{@link HttpServletRequest}、
	 * {@link HttpServletResponse}、{@link HttpSession}中的1个或多个.
	 *
	 * @throws FileUploadException
	 */
	private String executeRequest(HttpServletRequest request, HttpServletResponse response) throws FileUploadException {

		Method method = node.getMethod();

		if (method == null) {
			if (!node.getViewConfig().isEmpty()) { return node.getViewConfig().keySet().iterator().next(); }
			return null;
		}

		Object[] params = extractParameters(request, response, method);

		// asynchronized method
		if (node.isAsync()) {
			AsyncContext ctx = request.startAsync();
			ctx.start(new AsyncMethodExecutor(ctx, node, params));
			return null;
		}
		return executeMethod(node, params);
	}

	private Object[] extractParameters(HttpServletRequest request, HttpServletResponse response, Method method) {

		Parameter[] parameters = method.getParameters();
		Object[] params = new Object[parameters.length];
		MapJ requestMap = null;

		for (int i = 0; i < parameters.length; i++) {
			Parameter param = parameters[i];
			Class<?> clazz = param.getType();
			if (clazz.equals(Request.class)) {
				params[i] = wrapeRequest(request);
			} else if ((ServletRequest.class.isAssignableFrom(clazz))) {
				params[i] = request;
			} else if (ServletResponse.class.isAssignableFrom(clazz)) {
				params[i] = response;
			} else if (HttpSession.class.isAssignableFrom(clazz)) {
				params[i] = request.getSession();
			} else if (param.isAnnotationPresent(Param.class)) {
				Param para = param.getAnnotation(Param.class);
				params[i] = parseValue(clazz, Lang.nvl(request.getParameter(para.value()), para.dft()));
			} else {
				Object bean = null;
				if (requestMap == null) {
					requestMap = MapJ.mapRequest(request.getParameterMap());
				}
				try {
					bean = BeanFactory.create(clazz);
					BeanUtil.populate(bean, requestMap);
				} catch (Exception e) {
				}
				params[i] = bean;
			}
		}
		return params;
	}

	/**
	 * 转换类型 可转换的类型有 {@link Date} number boolean
	 */
	private Object parseValue(Class<?> clazz, String val) {

		if (CharSequence.class.isAssignableFrom(clazz)) { return val; }

		MapJ p = new MapJ();
		p.put("a", val);
		if (Date.class.isAssignableFrom(clazz)) { return p.getDate("a"); }
		if (Integer.class.isAssignableFrom(clazz) || clazz.equals(Integer.TYPE)) { return p.getInt("a"); }
		if (Double.class.isAssignableFrom(clazz) || clazz.equals(Double.TYPE)) { return p.getDouble("a"); }
		if (Boolean.class.isAssignableFrom(clazz) || clazz.equals(Boolean.TYPE)) { return p.getBoolean("a"); }

		return null;
	}

	private String executeMethod(PathNode node, Object[] params) {

		Method method = node.getMethod();
		Object target = null;

		if (Modifier.isStatic(method.getModifiers())) {
			target = method.getDeclaringClass();
		} else {
			if (node.isSingleton()) {
				try {
					target = BeanFactory.getBeanFactory(BeanFactory.PLATFORM_BEAN_FACTORY).get(
							"lion.framework$" + method.getDeclaringClass().getName(),
							method.getDeclaringClass());
				} catch (Throwable e) {
					throw new WebException(500, "Internal Server Error", e);
				}
			} else {
				try {
					target = BeanFactory.create(method.getDeclaringClass());
				} catch (Throwable e) {
					throw new WebException(500, "Internal Server Error", e);
				}
			}
		}

		// synchronized method
		Object result = null;
		// proxy with method level interceptors
		target = Proxy.proxy(target, new ExecuteInterceptor());
		try {
			method.setAccessible(true);
			result = method.invoke(target, params);
		} catch (Throwable e) {
			// 两层 ,一层为invok抛出,1层为Proxy抛出,然后是方法里面抛出的异常
			while (e instanceof InvocationTargetException) {
				e = e.getCause();
			}
			if (e instanceof WebException) { throw (WebException) e; }
			throw new WebException(500, e.getMessage(), e);
		}

		return (String) result;
	}

	private void executeView(HttpServletRequest request, HttpServletResponse response, String viewName) throws Exception {

		if (StringUtils.isBlank(viewName)) { return; }

		IView view = null;
		if (StringUtils.isNotBlank(viewName)) {
			view = node.getView(viewName);
		} else {
			view = new NoopView();
		}
		// 执行view返回
		if (view == null) {
			if (log.isDebugEnabled()) {
				log.debug(String.format("the view %s is not found", viewName));
			}
			throw new WebException(500, "view not found " + viewName);
		}

		try {
			view.render(request, response);
		} catch (Exception e) {
			throw e;
		}
	}

	private Request wrapeRequest(HttpServletRequest request) {

		Request result = new Request(request);
		// path param
		String pathPattern = node.toString();
		if (ParameterizedPath.isParameteredPath(pathPattern)) {
			MapJ pathParameter = ParameterizedPath.getParameters(pathPattern,
					request.getRequestURI().substring(request.getContextPath().length()));

			if (pathParameter != null && !pathParameter.isEmpty()) {
				result.putAll(pathParameter);
			}
		}

		return result;
	}

	public static void onException(HttpServletRequest request, HttpServletResponse response, Throwable exception) {

		// prevent infinite loop in case of exception in ExceptionHandler
		if (request.getAttribute("lion.framework.web.exception.handled") != null) {
			try {
				response.getWriter().write("Internal Server Error:" + exception.getMessage());
			} catch (IOException e1) {
			}
			return;
		}
		request.setAttribute("lion.framework.web.exception.handled", true);

		try {
			new ExceptionView(exception).render(request, response);
		} catch (Exception e1) {
		}
		Config config = ConfigManager.getConfig("framework");
		boolean dev = config.getBoolean("framework.devmode");
		if (dev) {
			log.error(exception.getMessage(), exception);
		}
	}
}
